/* Copyright (c) 2008 LepomisLab.com, Andrey Turkin
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "NSTableView.h"
#include "NSArrayController.h"
#include "NSKeyedUnarchiver.h"

class NSTableHeaderView : public NSView
{
	DECLARE_DYNAMIC_CLASS(NSTableHeaderView);
};
IMPLEMENT_DYNAMIC_CLASS(NSTableHeaderView, NSView)
class _NSCornerView : public NSView
{
	DECLARE_DYNAMIC_CLASS(_NSCornerView);
};
IMPLEMENT_DYNAMIC_CLASS(_NSCornerView, NSView)

IMPLEMENT_DYNAMIC_CLASS(NSTableView, NSView)

NSTableView::NSTableView() : NSView(), m_controller(NULL)
{
	_dc_target = _dc_argument = NULL;
	_content = _selindex = NULL;
	_grid = NULL;
	exposeBinding(wxT("doubleClickArgument"));
	exposeBinding(wxT("doubleClickTarget"));
//	exposeBinding(wxT("content"));
	exposeBinding(wxT("selectionIndexes"));
}

void NSTableView::initWithUnarchiver(NSKeyedUnarchiver *archiver)
{
	NSView::initWithUnarchiver(archiver);

	NSMutableArray* _columns = wxDynamicCast(archiver->decodeObjectForKey(wxT("NSTableColumns")), NSMutableArray);
	wxASSERT(_columns);
	m_nColumns = _columns->length();
	wxASSERT(m_nColumns > 0);

	for(size_t i = 0; i < m_nColumns; i++)
	{
		NSTableColumn* _column = wxDynamicCast(_columns->getObject(i), NSTableColumn);
		wxASSERT(_column);
		m_columns.push_back(_column);
		_column->_set_table(this);
	}

	if (archiver->contains(wxT("NSTvFlags")))
		_flags = archiver->decodeIntForKey(wxT("NSTvFlags"));

	if (archiver->contains(wxT("NSDataSource")))
		archiver->decodeObjectForKey(wxT("NSDataSource"));

	if (archiver->contains(wxT("NSDelegate")))
		archiver->decodeObjectForKey(wxT("NSDelegate"));

	if (archiver->contains(wxT("NSTarget")))
		archiver->decodeObjectForKey(wxT("NSTarget"));

	if (archiver->contains(wxT("NSAction")))
		archiver->decodeObjectForKey(wxT("NSAction"));

//	if (archiver->contains(wxT("NSCornerView")))
//		archiver->decodeObjectForKey(wxT("NSCornerView"));

	if (archiver->contains(wxT("NSHeaderView")))
	{
		archiver->decodeObjectForKey(wxT("NSHeaderView"));
	}
}

void NSTableView::_setValue(const wxString& key, wxObject* value)
{
	if (key == wxT("doubleClickArgument"))
	{
		_dc_argument = value;
	} else if (key == wxT("doubleClickTarget"))
	{
		_dc_target = value;
/*	} else if (key == wxT("content"))
	{
		_content = wxDynamicCast(value, NSMutableArray);
		wxASSERT(_content);
		_updateWidgetContent();
*/	} else if (key == wxT("selectionIndexes"))
	{
		_selindex = wxDynamicCast(value, NSMutableArray);
		wxASSERT(_selindex);
		_updateWidgetSelection();
	} else
		NSView::_setValue(key, value);
}

wxObject* NSTableView::_getValue(const wxString& key)
{
	if (key == wxT("doubleClickArgument"))
	{
		return _dc_argument;
	} else if (key == wxT("doubleClickTarget"))
	{
		return _dc_target;
/*	} else if (key == wxT("content"))
	{
		return _content;
*/	} else if (key == wxT("selectionIndexes"))
	{
		return _selindex;
	} else
		return NSView::_getValue(key);
}

void NSTableView::_releaseWidget(void)
{
	if (_grid)
	{
		_grid->Destroy();
		_grid = NULL;
	}
}

class MyGridTable : public wxGridTableBase
{
	typedef std::vector<NSMutableArray*> values_vector;
public:
	MyGridTable(table_columns& columns) : wxGridTableBase(), m_columns(columns), m_values(),  m_observer(this)
	{
		size_t n = 0;
		for(table_columns::const_iterator i = m_columns.begin(); i != m_columns.end(); i++, n++)
		{
			NSTableColumn* column = *i;
			m_observer.observe(column, n);
			NSMutableArray* arr = wxDynamicCast(column->getValue(wxT("value")), NSMutableArray);
			wxASSERT(arr);
			m_values.push_back(arr);
		}
		calculate_rows();
	}

	virtual int GetNumberRows()
	{
		return (int)m_rows;
	}

    virtual int GetNumberCols()
	{
		return (int)m_columns.size();
	}

	virtual bool IsEmptyCell( int row, int col )
	{
		if ((size_t)col>=m_columns.size())
			return true;
		if ((size_t)row >= m_values[col]->length())
			return true;
		return false;
	}
    virtual wxString GetValue( int row, int col )
	{
		wxCHECK((size_t)col<m_columns.size(), wxT(""));
		NSMutableArray* arr = m_values[(size_t)col];
		wxCHECK((size_t)row < arr->length(), wxT(""));
		wxVariant* v = wxDynamicCast(arr->getObject((size_t)row), wxVariant);
		wxCHECK(v, wxT(""));
		return v->GetString();
	}
    virtual void SetValue( int row, int col, const wxString& value )
	{
	}
private:
	void calculate_rows(void)
	{
		m_rows = 0;
		for(values_vector::const_iterator i = m_values.begin(); i != m_values.end(); i++)
		{
			NSMutableArray* arr = *i;
			wxASSERT(arr);
			if (m_rows < arr->length())
				m_rows = arr->length();
		}
	}
	void column_changed(size_t col)
	{
		NSMutableArray* old_column = m_values[col];
		NSMutableArray* new_column = wxDynamicCast(m_columns[col]->getValue(wxT("value")), NSMutableArray);
		wxCHECK_RET(new_column, wxT("Not an array in TableColumn!"));
		if (old_column == new_column)
			return;
		m_values[col] = new_column;
		if (old_column->length() != new_column->length())
		{
			size_t old_rows = m_rows;
			calculate_rows();
			if (old_rows != m_rows)
			{
				wxGridTableMessage msg;
				msg.SetTableObject(this);
				if (old_rows > m_rows)
				{
					msg.SetId(wxGRIDTABLE_NOTIFY_ROWS_DELETED);
					msg.SetCommandInt((int)m_rows);
					msg.SetCommandInt2((int)(old_rows-m_rows));
				} else
				{
					msg.SetId(wxGRIDTABLE_NOTIFY_ROWS_APPENDED);
					msg.SetCommandInt((int)(m_rows-old_rows));
				}
				GetView()->ProcessTableMessage(msg);
			}
		}
		for(size_t i = 0; i < new_column->length();i++)
		{
			wxGridTableMessage msg(this, wxGRIDTABLE_REQUEST_VIEW_GET_VALUES, (int)i, (int)col);
			GetView()->ProcessTableMessage(msg);
		}
	}

private:
	table_columns& m_columns;
	values_vector m_values;
	size_t m_rows;
	class column_observer : public NSObject
	{
		typedef std::map<size_t, NSObject*> observees_map;
	public:
		column_observer(MyGridTable* parent) : m_parent(parent), m_observees() {}
		~column_observer()
		{
			for(observees_map::const_iterator i = m_observees.begin(); i!=m_observees.end(); i++)
				i->second->removeObserver(this, wxT("value"));
		}
		void observe(NSObject* target, size_t context)
		{
			target->registerObserver(this, wxT("value"), 0, (void*)context);
			m_observees.insert(observees_map::value_type(context, target));
		}
		virtual void observeValueForKeypath(const wxString& keypath, NSObject* object, void* context)
		{
			wxASSERT(keypath == wxT("value"));NSUNUSED(keypath);
			size_t col = (size_t) context;
			wxASSERT(m_observees.find(col) != m_observees.end() && m_observees.find(col)->second == object);NSUNUSED(object);
			m_parent->column_changed(col);			
		}
	private:
		MyGridTable* m_parent;
		observees_map m_observees;
	} m_observer;
};

class TableGridEvt : public wxEvtHandler
{
public:
	TableGridEvt(NSTableView* tbl) : wxEvtHandler(), _tbl(tbl){}
	void OnClick(wxGridEvent& evt)
	{
		_tbl->OnDblClick(evt.GetCol(), evt.GetRow());
	}
	void OnSelection(wxGridEvent& evt)
	{
		_tbl->OnSelectionChange(evt.GetCol(), evt.GetRow());
	}
private:
	NSTableView *_tbl;
};

wxWindow* NSTableView::_allocWidget(NSView *parent)
{
	wxWindow* pwidget = parent->widget();
	if (pwidget)
	{
		wxGridTableBase* table = new MyGridTable(this->m_columns);
		_grid = new wxGrid(pwidget, wxID_ANY);
		_grid->BeginBatch();
		_grid->SetTable(table, true);
		int col = 0;
		for(table_columns::const_iterator i = m_columns.begin(); i != m_columns.end(); i++, col++)
		{
			NSTableColumn *cur_column = *i;
			_grid->SetColMinimalWidth(col, cur_column->_minWidth);
			_grid->SetColSize(col, cur_column->_width);
		}
		_grid->SetRowLabelSize(0);
		_grid->SetColLabelSize(0);
		_initWidget(_grid);
		TableGridEvt* evt = new TableGridEvt(this);
		_grid->Connect(wxID_ANY, wxID_ANY, wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler(TableGridEvt::OnClick), NULL, evt);
		_grid->Connect(wxID_ANY, wxID_ANY, wxEVT_GRID_SELECT_CELL, wxGridEventHandler(TableGridEvt::OnSelection), NULL, evt);
		_grid->EndBatch();
	}
	return _grid;
}

void NSTableView::_updateWidgetSelection()
{
	if (_grid)
	{
		_grid->BeginBatch();
		int rows = _selindex ? (int)_selindex->length() : NULL;
		_grid->ClearSelection();
		for(int i=0;i<rows;i++)
		{
			wxVariant* v = wxDynamicCast(_selindex->getObject(i), wxVariant);
			wxASSERT(v);
			_grid->SelectRow(v->GetInteger(), true);
		}
		_grid->EndBatch();
	}
}

void NSTableView::OnDblClick(int col, int row)
{
	NSUNUSED(col);
	NSUNUSED(row);
	bindingInvokeAction(wxT("doubleClickTarget"), wxT("doubleClickArgument"));
}

void NSTableView::OnSelectionChange(int col, int row)
{
	NSUNUSED(col);
	wxObject** a = new wxObject*[1];
	a[0] = new wxVariant(row);
	setValue(wxT("selectionIndexes"), new NSMutableArray(a, 1));
}

void NSTableView::_column_bound(NSObject *to)
{
	if (!m_controller)
	{
		NSArrayController* cntrl = wxDynamicCast(to, NSArrayController);
		if (cntrl)
		{
			m_controller = cntrl;
			bind(wxT("selectionIndexes"), m_controller, wxT("selectionIndexes"));
		}
	}
}
